/**
 * 
 */
package com.fox.webapp.service.impl;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;

import javax.annotation.PostConstruct;

import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import com.fox.webapp.domain.Account;
import com.fox.webapp.domain.Bank;
import com.fox.webapp.domain.CommissionRecord;
import com.fox.webapp.domain.Communicate;
import com.fox.webapp.domain.LoginLog;
import com.fox.webapp.domain.Person;
import com.fox.webapp.domain.SiteMessage;
import com.fox.webapp.domain.SystemSetting;
import com.fox.webapp.domain.WithdrawRecord;
import com.fox.webapp.persistent.AccountRepository;
import com.fox.webapp.persistent.BankRepository;
import com.fox.webapp.persistent.CommissionRecordRepository;
import com.fox.webapp.persistent.CommunicateRepository;
import com.fox.webapp.persistent.LoginLogRepository;
import com.fox.webapp.persistent.PersonRepository;
import com.fox.webapp.persistent.SiteMessageRepository;
import com.fox.webapp.persistent.SystemSettingRepository;
import com.fox.webapp.persistent.WithdrawRecordRepository;
import com.fox.webapp.persistent.impl.CommissionRecordRepositoryImpl;
import com.fox.webapp.service.IAsyncActionService;
import com.fox.webapp.service.IPersonService;
import com.fox.webapp.utils.Constant;
import com.fox.webapp.utils.StreamConvertUtil;
import com.fox.webapp.utils.ZXingEncoder;
import com.mongodb.gridfs.GridFSDBFile;
import com.mongodb.gridfs.GridFSFile;
import com.mongodb.gridfs.GridFSInputFile;

/**
 * @author yockii
 * 
 */
@Service
public class PersonService implements IPersonService {

	private static final Logger logger = LoggerFactory.getLogger(PersonService.class);
	@Autowired
	private PersonRepository personRepository;
	@Autowired
	private AccountRepository accountRepository;
	@Autowired
	private BankRepository bankRepository;
	@Autowired
	private WithdrawRecordRepository withdrawRecordRepository;
	@Autowired
	private LoginLogRepository loginLogRepository;
	@Autowired
	private CommissionRecordRepository commissionRecordRepository;
	@Autowired
	private SystemSettingRepository systemSettingRepository;
	@Autowired
	private CommunicateRepository communicateRepository;
	@Autowired
	private SiteMessageRepository siteMessageRepository;
	@Autowired
	private IAsyncActionService asyncActionService;

	@PostConstruct
	public void initPersonData(){
		Person p = new Person();
		p.setUsername("system");
		personRepository.save(p);
		p = new Person();
		p.setUsername("admin");
		personRepository.save(p);
		p = new Person();
		p.setUsername("administrator");
		personRepository.save(p);
	}
	
	@Override
	public boolean isExist(Person person) {
		return personRepository.isUsernameExist(person.getUsername()
				.toLowerCase()) != null
				|| (StringUtils.hasText(person.getEmail()) && personRepository
						.isEmailExist(person.getEmail().toLowerCase()) != null)
				|| (StringUtils.hasText(person.getMobile()) && personRepository
						.isMobileExist(person.getMobile()) != null);

	}

	@Override
	public Person addNewPerson(Person person) {
		person.setUsername(person.getUsername().toLowerCase());
		person.setEmail(person.getEmail().toLowerCase());
		
		person = personRepository.save(person);

		try{
			String pid = person.getReferenceId();
			if(ObjectId.isValid(pid)){
				Person parent = personRepository.findOne(new ObjectId(pid));
				if(parent != null){
					Person p = new Person();
					p.setReferenceId(pid);
					int refCount = personRepository.countWithCondition(p);
					if(refCount > 0){
						SystemSetting s = systemSettingRepository.findByKey(Constant.SYS_REFLEVEL);
						parent.setRefLevel(1);
						if(s != null && s.getValue() != null){
							String[] ss = s.getValue().split(",");
							for (int i = 0; i < ss.length; i++) {
								int c = Integer.valueOf(ss[i].trim());
								if (refCount > c) {
									parent.setRefLevel(i + 1);
									continue;
								}
								break;
							}
						}
						personRepository.save(parent);
					}
				}
			}
		} catch(Throwable t){
			t.printStackTrace();
		}
		return person;
	}

	@Override
	public Person login(String username, String password, String ip, boolean isAutoLogin) {
		Person p = personRepository.findByUsername(username.toLowerCase());
		if(p != null){
			// log
			LoginLog log = new LoginLog();
			log.setIpAddr(ip);
			log.setUsername(p.getUsername());
			log.setUseLoginUsername(isAutoLogin?Constant.STR_AUTO_LOGIN:username);
			log.setTime(new Date());
			loginLogRepository.save(log);
			
			if (p.getPassword().equals(password)) {
				return p;
			}
		}
		
		return null;
	}

	@Override
	public Person getPersonById(ObjectId userId) {
		return personRepository.findOne(userId);
	}

	@Override
	public Person modifySimpleInfo(Person person) {
		Person p = personRepository.findOne(person.getId());
//				personRepository.findByUsername(person.getUsername().toLowerCase());
		if (p.getEmail() == null
				|| !p.getEmail().equals(person.getEmail().toLowerCase())) {
			person.setEmailVerified(false);
		} else {
			person.setEmail(person.getEmail().toLowerCase());
		}
		if (p.getMobile() == null || !p.getMobile().equals(person.getMobile())) {
			person.setMobileVerified(false);
		}
		if (p.getPassword().equals(person.getPassword())) {
			person.setId(p.getId());
			return personRepository.updatePerson(person);
		}
		return null;
	}

	@Override
	public Person modifyPassword(Person p) {
		return personRepository.updatePersonOnlyPassword(p);
	}

	@Override
	public boolean updatePersonBank(Person p, Bank bank) {
		Bank b = p.getBank();
		if (b == null) {
			bank = bankRepository.save(bank);
			p.setBank(bank);
		} else {
			b.setBankName(bank.getBankName());
			b.setBankNo(bank.getBankNo());
			b.setName(bank.getName());
		}
		Person person = personRepository.updatePersonOnlyBank(p);
		return person.getBank().equals(bank);
	}

	@Override
	public Person verifyEmail(String username, String emailUUid) {
		if (StringUtils.hasText(username) && StringUtils.hasText(emailUUid)) {
			Person p = personRepository.findByUsername(username);
			if (p != null
					&& (p.getEmailVerified() == null || !p.getEmailVerified()
							.booleanValue())) {
				if (p.getEmailVerifyCode() != null
						&& emailUUid.equals(p.getEmailVerifyCode())) {
					p.setEmailVerified(true);
					p.setEmailVerifyCode("");
					personRepository.updatePerson(p);
					return p;
				}
			}
		}
		return null;
	}

	@Override
	public Person unVerifyEmail(String username, String uuid) {
		if (StringUtils.hasText(username) && StringUtils.hasText(uuid)) {
			Person p = personRepository.findByUsername(username);
			if(p != null && p.getEmail() != null && p.getEmailVerified() != null && p.getEmailVerified() && uuid.equals(p.getEmailModifyCode())){
				p.setEmailModifyCode("");
				p.setEmailVerifyCode("");
				p.setEmailVerified(false);
				personRepository.updatePerson(p);
				return p;
			}
		}
		return null;
	}

	@Override
	public Page<Person> getPersonWithCondition(Person person, int page, int length) {
//		return personRepository.findByUsernameOrEmailOrFirstNameOrLastNameOrMobileAllIgnoreCase(person.getUsername(), person.getEmail(), person.getFirstName(), person.getLastName(), person.getMobile(), new PageRequest(page, length));
		return personRepository.findPersonByCondition(person, new PageRequest(page, length));
	}

	@Override
	public int countPersonNumWithCondition(Person person) {
		return personRepository.countWithCondition(person);
	}

	@Override
	public Person updatePersonLoginIp(Person p) {
		if(p.getId() == null || p.getLastLoginIp() == null) return null;
		return personRepository.updatePersonOnlyLastLoginIp(p);
	}

	@Override
	public Page<Person> getMyReferences(Person me, int page, int length) {
		if(me == null || me.getId() == null) return null;
		Person p = new Person();
		p.setReferenceId(me.getId().toString());
		return personRepository.findPersonByCondition(p, new PageRequest(page, length, Direction.DESC, "registerTime"));
	}

	@Override
	public boolean withdraw(Person p, double withdraw, String ip) {
		if(withdraw <= 0 || p == null) return false;
		Bank bank = p.getBank();
		if(bank == null || !StringUtils.hasText(bank.getBankName()) || !StringUtils.hasText(bank.getBankNo())) return false;
		personRepository.decreaseBalance(p, withdraw);
		p.setTotalBalance(p.getTotalBalance() - withdraw);
		WithdrawRecord withdrawRecord = new WithdrawRecord();
		withdrawRecord.setApplyIp(ip);
		withdrawRecord.setApplyTime(new Date());
		withdrawRecord.setMoney(withdraw);
		withdrawRecord.setPerson(p);
		withdrawRecordRepository.save(withdrawRecord);
		return true;
	}

	@Override
	public int countWithdrawRecordNum(Person p) {
		if(p == null || p.getId() == null) return 0;
		WithdrawRecord condition = new WithdrawRecord();
		condition.setPerson(p);
		return withdrawRecordRepository.countWithCondition(condition);
	}

	@Override
	public Page<WithdrawRecord> getMyWithdrawRecords(Person p, int page,
			int length) {
		if(p == null || p.getId() == null) return null;
		WithdrawRecord condition = new WithdrawRecord();
		condition.setPerson(p);
		return withdrawRecordRepository.findWithCondition(condition, new PageRequest(page, length));
	}

	@Override
	public Page<CommissionRecord> findMyCommissionRecord(Person p, int page,
			int length) {
		if(p == null || p.getId() == null) return null;
		
		List<Account> accounts = accountRepository.findByPersonAndActiveIsTrue(p);
		
		return commissionRecordRepository.findAllMyAccountsCommissionRecord(accounts, new PageRequest(page, length, Direction.DESC, "time"));
	}
	
	@Override
	public Page<SiteMessage> getMyMessages(Person p, boolean isFrom, int page,
			int length) {
		if(p == null || p.getId() == null) return null;
		
		return isFrom ?
				siteMessageRepository.findByToUsername(p.getUsername(), new PageRequest(page, length, Direction.DESC, "sendTime"))
				: siteMessageRepository.findByFromUsername(p.getUsername(), new PageRequest(page, length, Direction.DESC, "sendTime"));
	}

	@Override
	public Person modifyQA(Person p) {
		if(p.getId() == null || p.getSecurityQuestion() == null || p.getSecurityAnswer() == null) return null;
		return personRepository.updatePersonOnlyQA(p);
	}

	@Override
	public boolean addNewCommunicate(Communicate communicate) {
		asyncActionService.sendErrorMessageAsync("标题: " + communicate.getTitle() + "<br/> 内容： <br/>" + communicate.getContent());
		
		return communicateRepository.save(communicate) != null;
	}

	@Override
	public String getSecurityQuestion(String username) {
		Person p = personRepository.findByUsername(username.toLowerCase());
		return p == null?"":p.getSecurityQuestion();
	}

	@Override
	public String canResetMyPassword(String username, String answer) {
		if(!StringUtils.hasText(username) || !StringUtils.hasText(answer)){
			return null;
		}
		Person p = personRepository.findByUsername(username.toLowerCase());
		if(p == null){
			return null;
		}
		if(!answer.equals(p.getSecurityAnswer())){
			return null;
		}
		String uuid = UUID.randomUUID().toString().replaceAll("-", "");
		p.setResetPasswordCode(uuid);
		personRepository.updateResetPasswordCode(p);
		return uuid;
	}

	@Override
	public boolean isPersonResetPasswordAvailable(String username,
			String resetUUid) {
		if(!StringUtils.hasText(username) || !StringUtils.hasText(resetUUid)){
			return false;
		}
		Person p = personRepository.findByUsername(username.toLowerCase());
		return resetUUid.equals(p.getResetPasswordCode());
	}

	@Override
	public boolean doResetPassword(String username, String uuid, String password) {
		if(!StringUtils.hasText(username) || !StringUtils.hasText(uuid) || !StringUtils.hasText(password)){
			return false;
		}
		Person p = personRepository.findByUsername(username.toLowerCase());
		if(!uuid.equals(p.getResetPasswordCode())){
			return false;
		}
		p.setPassword(password);
		personRepository.updatePersonOnlyPassword(p);
		return true;
	}

	@Override
	public Person findPersonByUsername(String username) {
		if(!StringUtils.hasText(username)){
			return null;
		}
		return personRepository.findByUsername(username);
	}

	@Override
	public boolean sendResetPasswordEmail(Person p) {
		if(p == null || p.getEmail() == null || p.getEmailVerified() == null || !p.getEmailVerified()){
			return false;
		}
		asyncActionService.sendResetPasswordEmail(p);
		return true;
	}

	@Override
	public boolean modifyVerifiedEmail(Person me) {
		if(me == null || me.getEmail() == null || me.getEmailVerified() == null || !me.getEmailVerified()){
			return false;
		}
		asyncActionService.sendModifyEmailEmail(me);
		return true;
	}

	@Override
	public void getMyQrcodeToStream(Person p, OutputStream outputStream) throws IOException {
		GridFSDBFile gf = personRepository.getQrcodeFile(p);
		if(gf == null){
			OutputStream out = new ByteArrayOutputStream();
			ZXingEncoder.qrEncode("http://www.econgen.com/?ref="+p.getId(), ZXingEncoder.BLACK, ZXingEncoder.WHITE, out);
			try{
				personRepository.storeQrcode(StreamConvertUtil.parse(out), p);
				gf = personRepository.getQrcodeFile(p);
			} catch (Exception e){
				e.printStackTrace();
			} finally {
				out.flush();
				out.close();
			}
		}
		if(gf != null){
			gf.writeTo(outputStream);
		}
	}

	@Override
	public Person getPersonByUsername(String username) {
		if(StringUtils.hasText(username)){
			return personRepository.findByUsername(username);
		}
		return null;
	}

	@Override
	public SiteMessage sendSiteMessage(SiteMessage siteMessage) {
		if(siteMessage == null){
			return null;
		}
		return siteMessageRepository.save(siteMessage);
	}
	
}
